/*
MIT License

Copyright (c) 2021 МГТУ им. Н.Э. Баумана, кафедра ИУ-6, Михаил Фетисов,

https://bmstu.codes/lsx/simodo/loom
*/

#include "simodo/variable/Module_interface.h"
#include "simodo/variable/VariableSetWrapper.h"
#include "simodo/inout/convert/functions.h"

#include <memory>
#include <filesystem>
#include <cassert>

#ifdef CROSS_WIN
// MinGW related workaround
#define BOOST_DLL_FORCE_ALIAS_INSTANTIATION
#endif

#include <boost/dll/alias.hpp>

using namespace simodo;
using namespace simodo::variable;
using namespace simodo::inout;

namespace fs = std::filesystem;

namespace
{
    Value setup(Module_interface * host, const VariableSetWrapper & args);
    Value produceTokens(Module_interface * host, const VariableSetWrapper & args);
}

class MainTokenizer : public Module_interface
{
    // ModuleFactory_interface *   _factory;

public:
    // MainTokenizer(ModuleFactory_interface * factory) : _factory(factory) {}

    Value setup(const std::string & path_to_data, const std::string & language);
    Value produceTokens(const std::u16string & text_to_parse, int position, context_index_t context);

    virtual version_t version() const override { return lib_version(); }

    virtual Object instantiate(std::shared_ptr<variable::Module_interface> module_object) override
    {
        return Object {{
            // {u"version", u"0.1"},
            {u"specialization", u"Comment"},
            {u"setup", {ValueType::Function, Object {{
                {u"@", ExternalFunction {module_object, ::setup}},
                {{}, ValueType::String},
                {u"path_to_data", ValueType::String},
                {u"language", ValueType::String},
            }}}},
            {u"produceTokens", {ValueType::Function, Object {{
                {u"@", ExternalFunction {module_object, ::produceTokens}},
                {{}, ValueType::Object},
                {u"text_to_parse", ValueType::String},
                {u"position", ValueType::Int},
                {u"context", ValueType::Int},
            }}}},
        }};
    }

    // virtual ModuleFactory_interface * factory() override { return _factory; }

    // Factory method
    static std::shared_ptr<Module_interface> create() {
        return std::make_shared<MainTokenizer>();
    }
};

BOOST_DLL_ALIAS(
    MainTokenizer::create,    // <-- this function is exported with...
    create_simodo_module      // <-- ...this alias name
)

namespace
{
    Value setup(Module_interface * host, const VariableSetWrapper & args)
    {
        // Эти условия должны проверяться в вызывающем коде и при необходимости выполняться преобразования
        assert(host != nullptr);
        assert(args.size() == 2);
        assert(args[0].value().type() == ValueType::String);
        assert(args[1].value().type() == ValueType::String);

        MainTokenizer * main = static_cast<MainTokenizer *>(host);
        return main->setup(toU8(args[0].value().getString()),
                           toU8(args[1].value().getString()));
    }

    Value produceTokens(Module_interface * host, const VariableSetWrapper & args)
    {
        // Эти условия должны проверяться в вызывающем коде и при необходимости выполняться преобразования
        assert(host != nullptr);
        assert(args.size() == 3);
        assert(args[0].value().type() == ValueType::String);
        assert(args[1].value().type() == ValueType::Int);
        assert(args[2].value().type() == ValueType::Int);

        MainTokenizer *        main = static_cast<MainTokenizer *>(host);
        context_index_t context = NO_TOKEN_CONTEXT_INDEX;
        if (args[2].value().getInt() >= 0 )
            context = static_cast<context_index_t>(args[2].value().getInt());

        return main->produceTokens(args[0].value().getString(), args[1].value().getInt(), context);
    }

}

Value MainTokenizer::setup(const std::string & /*path_to_data*/, const std::string & language)
{
    /// @todo Дополнить список поддерживаемых языков
    /// @todo Вынести перечень поддерживаемых языков в JSON-файл.
    using namespace std::literals;
    for(const std::string & ext : {"cpp"s, "java"s})
        if (ext == language)
            return u""; // ОК

    return {}; // Не ОК
}

Value MainTokenizer::produceTokens(const std::u16string & text_to_parse, int position, context_index_t )
{
    /// @todo Нужно проверять только DOXYGEN-комментарии.
    /// Не забыть учесть разрывы комментариев на несколько строк.

    /// @todo Нужно дополнить список ключевых слов DOXYGEN.
    /// @todo Нужно вынести ключевые слова в JSON-файл. Для этого использовать метод setup.
    std::vector<std::u16string> doxy_keys {
        u"brief", u"todo", u"details", u"attention", u"note", u"file", u"author", u"version",
        u"date", u"copyright", u"param", u"return", u"deprecated",
    };

    std::u16string::size_type pos;
    for(pos=0; pos < text_to_parse.size(); ++pos)
        if (text_to_parse[pos] == u'\\' || text_to_parse[pos] == u'@') {
            ++pos;
            break;
        }

    /// @todo Вообще-то, нужно анализировать не только ключевые слова, но и элементы форматирования.
    /// Хотя, вероятно, это будет задача для отдельного токенайзера.

    Array tokens;
    while(pos < text_to_parse.size()) {
        for(const std::u16string & key : doxy_keys)
            /// @todo Сейчас проверяется только нижний регистр. Нужно уточнить: является ли
            /// DOXYGEN регистрозависимым.
            if (text_to_parse.substr(pos,key.size()) == key) {
                /// @todo Вообще-то, нужно весь комментарий DOXYGEN выделять, как документ,
                /// а не как обычный комментарий (у простого комментария и документа семантика разная).
                /// Сейчас подсвечиваются только ключевые слова.
                /// Но для этого нужно выделять только DOXYGEN-комментарии (см. todo выше).
                Object token_data {{
                    {u"token",    text_to_parse.substr(pos-1, key.size()+1)},
                    {u"type",     u"Doxygen-keyword"},
                    {u"position", position + static_cast<int64_t>(pos-1)},
                    }};
                tokens.values().emplace_back(token_data);
                pos += key.size();
                break;
            }

        /// @todo Дублирование кода (см. выше), нужно ли убирать?
        for(; pos < text_to_parse.size(); ++pos)
            if (text_to_parse[pos] == u'\\' || text_to_parse[pos] == u'@') {
                ++pos;
                break;
            }
    }

    return Object {{
        {u"tokens", tokens},
        }};
}

